package com.lam.mall.api.controller;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.lam.common.base.domain.LoginUser;
import com.lam.common.base.domain.Result;
import com.lam.common.constant.CommonConstant;
import com.lam.common.context.AuthTypeEnum;
import com.lam.common.context.TokenUtils;
import com.lam.common.exception.BusinessException;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.Md5Util;
import com.lam.common.utils.NumberUtil;
import com.lam.common.utils.PasswordUtil;
import com.lam.common.utils.RegexUtil;
import com.lam.common.utils.passwd.PasswordCheckManager;
import com.lam.common.utils.passwd.PasswordManager;
import com.lam.common.utils.uuid.IdUtils;
import com.lam.common.validate.constant.ValidConstant;
import com.lam.framework.operLog.annotation.Log;
import com.lam.framework.operLog.enums.BusinessType;
import com.lam.mall.app.param.UserRegisterParam;
import com.lam.mall.app.vo.AuthenticationVo;
import com.lam.mall.entity.MallUser;
import com.lam.mall.service.MallUserService;

import cn.hutool.core.util.StrUtil;

/**
 */
@Log(title = "前端登录注册")
@RestController
@RequestMapping("/${frontPath}/")
public class LoginController {
	
	private final Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
    private MallUserService mallUserService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统
     * @param authenticationDTO
     * @return
     */
    @PostMapping("/login")
    @Log(oper = "账号密码登录", businessType = BusinessType.LOGIN_IN)
    public Result login(@Valid @RequestBody AuthenticationVo authenticationVo) {
        String mobileOrUserName = authenticationVo.getUserName();
        
        MallUser mallUser = null;
        // 手机验证码登陆，或传过来的账号很像手机号
        if (RegexUtil.isMobile(mobileOrUserName)) {
        	mallUser = mallUserService.selectByPhone(mobileOrUserName);
        }
        // 如果不是手机验证码登陆， 找不到手机号就找用户名
        if  (mallUser == null) {
        	mallUser = mallUserService.selectByUsername(mobileOrUserName);
        }
        if (mallUser == null) {
        	logger.error("用户登录账号或密码不正确, mobileOrUserName: {}", mobileOrUserName);
            throw new BusinessException("账号或密码不正确");
        }
        if(!NumberUtil.eq(CommonConstant.STATUS_NORMAL_1, mallUser.getStatus())) {
        	logger.error("用户登录账户被禁用, mobileOrUserName: {}", mobileOrUserName);
        	throw new BusinessException("账户已被禁用！");
        }

        String decryptPassword = PasswordManager.decryptPassword(authenticationVo.getPassWord());
        String userpassword = PasswordUtil.encrypt(mallUser.getUserCode(), Md5Util.md5Encrypt(decryptPassword), mallUser.getSalt());
        
        // 半小时内密码输入错误十次，已限制登录30分钟
//        PasswordCheckManager.checkPassword(AuthTypeEnum.FRONT, mobileOrUserName, decryptPassword, mallUser.getLoginPassword());
        PasswordCheckManager.checkPassword(AuthTypeEnum.FRONT, mobileOrUserName, userpassword, mallUser.getLoginPassword());
        
        LoginUser loginUser = createLoginUserInfo(mallUser);
		// 获取用户
    	Map<String, Object> obj = new HashMap<>();
		// 生成token
		obj.put("token", TokenUtils.createToken(loginUser));
		obj.put("nickName", loginUser.getNickName());
		obj.put("exp", loginUser.getTokenExpires());
		
		logger.info("前端用户登录成功! username:{}, userType：{}", loginUser.getUsername(), loginUser.getUserType());
		return Result.ok("登录成功！").data(obj);
    }
    
    private LoginUser createLoginUserInfo(MallUser user ) {
    	LoginUser loginUser = JsonUtil.toJavaObject(user, LoginUser.class, "id=>userId", "username", "regType", "nickName", "realname", 
    			"email", "phone", "avatar", "sex", "status", "userType", "isNew", "remark");
    	
    	//加载用户的角色和权限
    	loginUser.setAuthType(AuthTypeEnum.FRONT.getAuthType());
    	loginUser.setRegType(CommonConstant.REG_TYPE_PASSWD);
    	TokenUtils.setCleintInfo(loginUser);

    	loginUser.setRememberMe(false);
    	loginUser.setTokenExpires(TokenUtils.TOKEN_EXPIRE_TIME_7_DAY);
		
		return loginUser;
    }

    @PostMapping("/loginOut")
    @Log(oper = "退出登录", businessType = BusinessType.LOGIN_OUT)
    public Result loginOut(HttpServletRequest request) {
		LoginUser loginUser = TokenUtils.getLoginUser(request);
		if (loginUser != null) {
			// 删除用户缓存记录
			TokenUtils.logout(loginUser.getToken());
		}
		return Result.ok("已退出！");
    }
    
    /**
     * 用户注册或绑定手机号接口
     * @return
     */
    @PostMapping("/register")
    @Log(oper = "注册", businessType = BusinessType.INSERT)
    public Result register(@Valid @RequestBody UserRegisterParam userRegisterParam) {
        if (StrUtil.isBlank(userRegisterParam.getNickName())) {
            userRegisterParam.setNickName(userRegisterParam.getUserName());
        }
        if(!userRegisterParam.getUserName().matches(ValidConstant.USER_NAME)) {
        	throw new BusinessException("用户名只能是数字或字母或下划线！");
        }
        
        String decryptPassword = PasswordManager.decryptPassword(userRegisterParam.getPassWord());
        if(decryptPassword.length() < 8 || decryptPassword.length() > 32) {
        	throw new BusinessException("密码长度为8-32个字符！");
        }
        if(!decryptPassword.matches(ValidConstant.USER_PWD)) {
        	throw new BusinessException("密码只能是数字或字母或_.$#@!");
        }
        
        
        
        // 正在进行申请注册
        MallUser dbMallUser = mallUserService.selectByUsername(userRegisterParam.getUserName());
        if(dbMallUser != null) {
        	throw new BusinessException("该用户名已注册！");
        }
        
        Date now = new Date();
        MallUser mallUser = new MallUser();
        mallUser.setUserCode(IdUtils.simpleUUID());
        mallUser.setUsername(userRegisterParam.getUserName());
        mallUser.setUpdateTime(now);
        mallUser.setCreateTime(now);
        mallUser.setStatus(CommonConstant.STATUS_NORMAL_1);
        mallUser.setEmail(userRegisterParam.getUserMail());
        mallUser.setSalt(IdUtils.genRandom(8));
        mallUser.setLoginPassword(PasswordUtil.encrypt(mallUser.getUserCode(), Md5Util.md5Encrypt(decryptPassword), mallUser.getSalt()));
//        mallUser.setLoginPassword(passwordEncoder.encode(decryptPassword));
        
        mallUserService.insert(mallUser);
        
        // 2. 登录
        LoginUser loginUser = createLoginUserInfo(mallUser);
		// 获取用户
    	Map<String, Object> obj = new HashMap<>();
		// 生成token
		obj.put("token", TokenUtils.createToken(loginUser));
		obj.put("exp", loginUser.getTokenExpires());
		obj.put("nickName", loginUser.getNickName());
		obj.put("pic", loginUser.getAvatar());
		return Result.ok("注册成功！").data(obj);
    }


    @PutMapping("/updatePwd")
    @Log(oper = "修改密码", businessType = BusinessType.UPDATE)
    public Result updatePwd(@Valid @RequestBody UserRegisterParam userPwdUpdateParam) {
    	
    	MallUser mallUser = mallUserService.selectById(TokenUtils.getLoginUserid());
        if (mallUser == null) {
            throw new BusinessException("用户信息异常");
        }
        String decryptPassword = PasswordManager.decryptPassword(userPwdUpdateParam.getPassWord());
        if (StrUtil.isBlank(decryptPassword)) {
            // 新密码不能为空
            throw new BusinessException("新密码不能为空");
        }
        String password = passwordEncoder.encode(decryptPassword);
        if (StrUtil.equals(password, mallUser.getLoginPassword())) {
            // 新密码不能与原密码相同
            throw new BusinessException("新密码不能与原密码相同");
        }
        mallUser.setUpdateTime(new Date());
        mallUser.setLoginPassword(password);
        mallUserService.update(mallUser);
        return Result.ok();
    }
}
